﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;

namespace Weazel.Willows
{
	public class SystemGenerator
	{
		private ImplementationConfiguration configuration;
		public ImplementationConfiguration Configuration
		{
			get { return configuration; }
		}

		private Weazel.Willows.PreliminaryModel.Model preliminaryModel;
		private Weazel.Willows.Model.Model model;
		private Weazel.Willows.RefinedModel.Model refinedModel;

		private Dictionary<string, Weazel.Badger.ConversionEntity> hardwareComponents;

		/// <summary>
		/// List of errors encountered when trying to read
		/// the configuration file.
		/// </summary>
		private List<ConfigurationError> configurationErrors;
		public List<ConfigurationError> ConfigurationErrors
		{
			get { return configurationErrors; }
		}

		/// <summary>
		/// List of errors encountered while creating the model.
		/// </summary>
		private List<ModelError> modelErrors;
		public List<ModelError> ModelErrors
		{
			get { return modelErrors; }
		}

		public SystemGenerator()
		{
		}

		#region Read configuration file
		private List<ConfigurationError> readConfigurationFile(string configurationFile)
		{
			List<ConfigurationError> errors = new List<ConfigurationError>();

			// check that the configuration actually exists
			if (!File.Exists(configurationFile))
			{
				string message =
					String.Format("Could not find configuration file '{0}'.", configurationFile);

				errors.Add(new ConfigurationError(message));
				return errors;
			}

			// read the configuration file
			FileStream stream = null;
			StreamReader reader = null;
			string content = null;

			try
			{
				stream = File.Open(configurationFile, FileMode.Open);
				reader = new StreamReader(stream);
				content = reader.ReadToEnd();
			}
			catch (System.IO.FileNotFoundException)
			{
				string message =
					String.Format("Could not find configuration file '{0}.'",
					configurationFile);

				errors.Add(new ConfigurationError(message));
				return errors;
			}
			finally
			{
				if (reader != null)
					reader.Close();
				if (stream != null)
					stream.Close();
			}

			// try to parse the content as xml 
			XmlDocument document = new XmlDocument();

			try
			{
				document.LoadXml(content);
			}
			catch (System.Xml.XmlException e)
			{
				errors.Add(new ConfigurationError(e.Message));
				return errors;
			}

			// next, read the xml into a configuration object
			if (document["Configuration"] == null)
			{
				string message = "Files does not contain a 'Configuration' element";
				errors.Add(new ConfigurationError(message));
				return errors;
			}

			configuration = new ImplementationConfiguration();
			configuration.Read(ref errors, document["Configuration"]);

			return errors;
		}
		#endregion

		/// <summary>
		/// This method loads the design specified in the configuration 
		/// file.
		/// </summary>
		private void loadDesign(List<ModelError> errors)
		{			
			// load the content of the design files into the model
			foreach (string inputFile in configuration.InputFiles)
			{
				// we have previously check if the files actually exist

				FileStream stream = null;
				StreamReader reader = null;
				string content = null;

				try
				{
					stream = File.Open(inputFile, FileMode.Open);
					reader = new StreamReader(stream);
					content = reader.ReadToEnd();
				}
				finally
				{
					if (reader != null)
						reader.Close();

					if (stream != null)
						stream.Close();
				}

				// parse as XML
				XmlDocument document = new XmlDocument();

				try
				{
					document.LoadXml(content);
				}
				catch (System.Xml.XmlException e)
				{
					string message =
						String.Format(
							"XML parse error encountered while parsing file '{0}. Message: {1}.'",
							inputFile,
							e.Message
						);

					errors.Add(new ModelError(message));

					continue;
				}

				// read model from xml description
				if (document["Model"] == null)
				{
					string message =
						String.Format("File '{0}' does not contain a Model element.", inputFile);

					errors.Add(new ModelError(message));
				}
				else
				{
					foreach (XmlElement description in document.GetElementsByTagName("Model"))
					{
						preliminaryModel =
							new Weazel.Willows.PreliminaryModel.Model(ref errors, description);
					}
				}

				Console.WriteLine("loaded file: " + inputFile);
			}
		}

		private void cleanDirectory(List<ConfigurationError> errors, string directory)
		{
			if (!Directory.Exists(directory))
			{
				string message = 
					string.Format("Cannot clean directory '{0}' - it does not exist", directory);

				errors.Add(new ConfigurationError(message));
			}

			DirectoryInfo dir = new DirectoryInfo(directory);

			foreach (FileInfo file in dir.GetFiles())
			{
				try
				{
					file.Delete();
				}
				catch (Exception e)
				{
					string message =
						string.Format("Cannot delete file '{0}'. Probably locked.", file.Name);

					errors.Add(new ConfigurationError(message));
				}
			}
		}

		private void createDirectory(List<ConfigurationError> errors, string directory)
		{
			if (Directory.Exists(directory))
				return;

			// try to create the destination directory
			try
			{
				Directory.CreateDirectory(directory);
			}
			catch (ArgumentException)
			{
				// ArgumentException thrown if desitnation has invalid characters 		
				string message =
					string.Format(
						"Cannot create directory '{0}'.",
						directory
					);

				errors.Add(new ConfigurationError(message));
				return;
			}
			catch (IOException)
			{
				string message =
					string.Format(
						"Unable to create directory '{0}'.",
						directory
					);

				errors.Add(new ConfigurationError(message));
				return;
			}
		}

		/// <summary>
		/// Creates the directory where the output of this tool is written. If the 
		/// directory exists it is deleted.
		/// </summary>
		/// <param name="errors">Reference to list of errors to append any 
		/// errors encountered</param>
		private void prepareDestinationDirectory(List<ConfigurationError> errors)
		{
			createDirectory(errors, configuration.DestinationDirectory);
			
			// create a directory for the implementation
			createDirectory(errors, configuration.ImplementationDirectory);
			cleanDirectory(errors, configuration.ImplementationDirectory);

			// .. and a sub-directory for the hardware part of the implementation
			createDirectory(errors, configuration.HardwareImplementationDirectory);
			cleanDirectory(errors, configuration.HardwareImplementationDirectory);

			// .. and a sub directory of the hw dir for the ISE project
			createDirectory(errors, configuration.IseProjectDirectory);

			// .. and one for the sortware part
			createDirectory(errors, configuration.SoftwareImplementationDirectory);
			cleanDirectory(errors, configuration.SoftwareImplementationDirectory);

			// create a directory to hold a gezel design for simulation purposes
			createDirectory(errors, configuration.SimulationDirectory);
			cleanDirectory(errors, configuration.SimulationDirectory);
		}

		private void writeGezelModel(string file)
		{
			FileStream stream = null;
			StreamWriter writer = null;

			try
			{
				stream = File.Create(file);
				writer = new StreamWriter(stream);
				writer.Write(Weazel.Gezel.Exporter.ModelExporter.ToGezelString(refinedModel));
				writer.Flush();
			}
			finally
			{
				if (writer != null)
					writer.Close();

				if (stream != null)
					stream.Close();
			}
		}

		private void generateHardware()
		{
			Weazel.Badger.Configuration configuration =
				new Weazel.Badger.Configuration();

			configuration.SystemToConvert = "top_level";
			configuration.CreateXilinxIseProjectFile = false;

			Weazel.Badger.ConversionContext conversion = 
				new Weazel.Badger.ConversionContext(refinedModel, configuration);

			// convert but do not write any files
			hardwareComponents = conversion.Convert(false);

			Console.WriteLine("Converted entities:");
			foreach (string key in hardwareComponents.Keys)
				Console.WriteLine("\t" + key);
		}

		private void generateInterface()
		{
			Weazel.Willows.InterfaceGeneration.IpifImplementation ipif =
				new Weazel.Willows.InterfaceGeneration.IpifImplementation(model, hardwareComponents["hardware"]);

			List<Model.Queue> hardwareToSoftwareQueues = new List<Weazel.Willows.Model.Queue>();
			List<Model.Queue> softwareToHardwareQueues = new List<Weazel.Willows.Model.Queue>();

			foreach(Model.Channel channel in model.Top.Channels.Values)
			{
				if(channel is Model.Queue)
				{
					Model.Queue queue = channel as Model.Queue;

					if(queue.GetWritingDatapath().PartitionChoice == PartitionChoice.Hardware)
						hardwareToSoftwareQueues.Add(queue);
					else if(queue.GetWritingDatapath().PartitionChoice == PartitionChoice.Software)
						softwareToHardwareQueues.Add(queue);
					else
						Console.WriteLine("Warning: Missing partition for writer module of Queue '{0}'", queue.LocalName);
				}
				else if(channel is Model.Signal)
				{
					Console.WriteLine("Warning: Design has synchrounous signal '{0}' at top level", channel.LocalName);
				}
				else
					throw new NotSupportedException();
			}

			ipif.Generate(hardwareToSoftwareQueues, softwareToHardwareQueues);

			ipif.Write(configuration);
		}

		private bool verifyRefinedModel()
		{
			List<Weazel.Gezel.Model.Errors.VerificationError> verificationErrors =
				new List<Weazel.Gezel.Model.Errors.VerificationError>();

			List<Weazel.Gezel.Model.Errors.VerificationWarning> verificationWarnings =
				new List<Weazel.Gezel.Model.Errors.VerificationWarning>();

			refinedModel.Verify(ref verificationErrors, ref verificationWarnings);

			foreach (Weazel.Gezel.Model.Errors.VerificationError error in verificationErrors)
			  Console.WriteLine(error.ErrorMessage + "\n");

			foreach (Weazel.Gezel.Model.Errors.VerificationWarning error in verificationWarnings)
			  Console.WriteLine(error.ErrorMessage + "\n");

			return verificationErrors.Count == 0 && verificationWarnings.Count == 0;
		}

		public bool Run(string configurationFile)
		{
			// read configuration file, abort if errors
			configurationErrors = readConfigurationFile(configurationFile);
			if (configurationErrors.Count > 0)
				return false;

			// create the destination directory
			prepareDestinationDirectory(configurationErrors);
			if (configurationErrors.Count > 0)
				return false;

			modelErrors = new List<ModelError>();

			// load design files, abort if errors
			loadDesign(modelErrors);
			if (modelErrors.Count > 0)
				return false;
			
			// convert to actual model
			model = preliminaryModel.Expand(modelErrors, configuration.SystemToConvert);
			if (modelErrors.Count > 0)
				return false;

			model.WriteChannelHierarchyToFile("one.txt");
			model.WriteHierarchyToFile("one_model.txt");

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                 SETTING PARTITION CHOICES                  |");
			Console.WriteLine("`------------------------------------------------------------'");

			model.SetPartitionChoice(configuration.Partition);

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                         PARTITION                          |");
			Console.WriteLine("`------------------------------------------------------------'");

			model.Partition();

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                         REFINING                           |");
			Console.WriteLine("`------------------------------------------------------------'");

			refinedModel = model.Refine();

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                  VERIFYING REFINED MODEL                   |");
			Console.WriteLine("`------------------------------------------------------------'");

			if (verifyRefinedModel())
				Console.WriteLine(" No errors found");

			Console.WriteLine(" Writing Gezel model to file 'gezel.fdl'");
			writeGezelModel(configuration.SimulationDirectory + "//gezel.fdl");

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                    GENERATING HARDWARE                     |");
			Console.WriteLine("`------------------------------------------------------------'");
		
			generateHardware();

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                    GENERATING SOFTWARE                     |");
			Console.WriteLine("`------------------------------------------------------------'");
			Console.WriteLine(" Not implemented");

			Console.WriteLine(".------------------------------------------------------------.");
			Console.WriteLine("|                    GENERATING INTERFACE                    |");
			Console.WriteLine("`------------------------------------------------------------'");

			generateInterface();
			

			return true;
		}
	}
}
